import pandas as pd
import numpy as np
from sklearn.preprocessing import OneHotEncoder
from sklearn.preprocessing import LabelEncoder
import random
import datetime
from deprecated.sphinx import deprecated
from config import Variables, DRLParameters
from learning.eval_helper import Measurement


class BaseTrace:
    def __init__(self, parameter_space: DRLParameters):
        self._df = parameter_space.get_data_frame()
        self._label_encoder = LabelEncoder()                                         # 活动类别编码
        self._one_hot_encoder = OneHotEncoder(sparse=False)
        self._week_encoder = self._train_week_encoder()                              # 星期编码
        self.activity_space = self._train_encoders()                                 # 活动空间大小
        self.all_case_ids = list(set(self._df['case:concept:name'].values))
        if parameter_space.DEBUG:
            self.train_case_ids = np.array(parameter_space.INITIAL_CASE_IDS)
            self.test_case_ids = np.array(parameter_space.INITIAL_CASE_IDS)
        else:
            self.train_case_ids = np.load(parameter_space.TRAIN_CASE_ID_PATH).tolist()
            self.test_case_ids = np.load(parameter_space.TEST_CASE_ID_PATH).tolist()

    def _train_encoders(self):
        """ 训练活动类别编码器
        :return: 活动类别数量
        """
        activity_list = list(set(self._df['concept:name'].values))
        labels = self._label_encoder.fit_transform(activity_list)
        labels = labels.reshape(len(labels), 1)
        self._one_hot_encoder.fit(labels)
        return len(activity_list)

    @staticmethod
    def _train_week_encoder():
        """ 训练星期编码器
        :return:
        """
        weeks = np.array(list(range(0, 7)))
        weeks = np.reshape(weeks, (weeks.shape[0], 1))
        week_encoder = OneHotEncoder(sparse=False)
        week_encoder.fit(weeks)
        return week_encoder

    def random_pick_case_id(self):
        """ 随机从训练集中选择一个case id
        :return:
        """
        return random.choice(self.train_case_ids)

    @deprecated(version="0.0.0", reason="此方法废弃")
    def split_dataset(self, seed=0, split=0.7):
        """ 分割数据集
        :param seed: 随机种子
        :param split: 训练集比例
        :return:
        """
        random.seed(seed)
        train_size = int(len(self.all_case_ids) * split)
        train_set = random.sample(population=self.all_case_ids, k=train_size)
        test_set = [x for x in self.all_case_ids if x not in train_set]
        np.save(Variables.get_train_case_id_path(), np.array(train_set), allow_pickle=True)
        np.save(Variables.get_test_case_id_path(), np.array(test_set), allow_pickle=True)

    def standard_amount_req(self, req_data):
        """ 标准化贷款金额
        :param req_data:
        :return:
        """
        ordered_amount_req = list(set(self._df["case:AMOUNT_REQ"].values))
        ordered_amount_req = sorted(ordered_amount_req)
        return (req_data-ordered_amount_req[0]) / (ordered_amount_req[-1]-ordered_amount_req[0])


class Trace(BaseTrace):
    def __init__(self, parameter_space: DRLParameters, case_id=None):
        super(Trace, self).__init__(parameter_space)
        self.case_id = case_id
        if self.case_id is None:
            self.case_id = self.random_pick_case_id()
        self._trace_df = self._df[self._df['case:concept:name'] == self.case_id]
        self.trace_length: int = self._trace_df.shape[0]                               # trace的长度
        self._activity_names = self._trace_df['concept:name'].values                   # 活动名称的序列
        self._timestamp_sequence = self._trace_df["time:timestamp_short"].values       # 时间戳序列

        # 特征值
        self.time_sequence = np.array(self._trace_df['time_spent'].values)             # 花费时间的序列
        self.prediction_sequence = np.array(self._trace_df['total_time_pred'].values)  # 预测值的序列
        self.activity_sequence = self._encode_activities()                             # 活动名称的one-hot编码序列
        # self.amount_req = self.standard_amount_req(self._trace_df['case:AMOUNT_REQ'].values[0])  # 贷款金额
        self.last_act_time_diff = self._time_gap_feature()                             # 离上个活动的时间间隔
        self.midnight_time_diff = self._midnight_feature()                             # 离午夜的时间间隔
        self.weekday = self._weekday_feature()                                         # 星期的one-hot编码

        # 真实值
        self.true_value = self._trace_df['total_time_true'].values[0]                  # 真实值
        self.baseline_area = self.covered_area(list(range(0, self.trace_length)))      # 预测点与真实值围成的面积

    def _encode_activities(self):
        """ 编码活动类别
        :return: 活动名称的one-hot编码
        """
        labels = self._label_encoder.transform(self._activity_names)
        labels = labels.reshape(len(labels), 1)
        return np.array(self._one_hot_encoder.transform(labels))

    @deprecated(version="0.0.0", reason="设置误差权重")
    def covered_area_v1(self, prediction_points, has_last_point=True):
        """ 计算预测值序列和真实值围成的面积
        :param prediction_points: 预测位置的下标
        :param has_last_point: prediction_points中是否包含有最后一个点
        :return: 面积
        """
        if not has_last_point:
            prediction_points.append(self.trace_length - 1)
        area = 0
        for i in range(1, len(prediction_points)):
            last_index = prediction_points[i-1]
            this_index = prediction_points[i]
            last_pred = self.prediction_sequence[last_index]
            last_t = self.time_sequence[last_index]
            this_t = self.time_sequence[this_index]
            area += abs(last_pred-self.true_value) * (this_t - last_t)
        return area

    def covered_area(self, prediction_points, has_last_point=True, alpha=0):
        if not has_last_point:
            prediction_points.append(self.trace_length - 1)

        x_labels = []
        y_pres = []
        for i in range(len(prediction_points)):
            x_labels.append(self.time_sequence[prediction_points[i]])
            y_pres.append(self.prediction_sequence[prediction_points[i]])
        return Measurement.AILoss(np.array(x_labels), np.array(y_pres), self.true_value, alpha=alpha)

    def _time_gap_feature(self):
        """ 和上一活动的时间差特征
        :return:
        """
        feature_value = [0]
        for i in range(1, len(self.time_sequence)):
            feature_value.append(self.time_sequence[i]-self.time_sequence[i-1])
        return np.array(feature_value)

    def _midnight_feature(self):
        """ 和午夜的时间差特征
        :return:
        """
        feature_value = []
        for dt_time in self._timestamp_sequence:
            event_dt = datetime.datetime.strptime(dt_time, '%Y-%m-%d %H:%M:%S')
            midnight_time = event_dt.replace(hour=0, minute=0, second=0, microsecond=0)
            feature_value.append((event_dt - midnight_time).total_seconds()/3600/24)
        return np.array(feature_value)

    def _weekday_feature(self):
        """ 星期特征
        :return:
        """
        feature_value = []
        for dt_time in self._timestamp_sequence:
            event_dt = datetime.datetime.strptime(dt_time, '%Y-%m-%d %H:%M:%S')
            weekday = event_dt.weekday()
            weekday = np.reshape(np.array([weekday]), (-1, 1))
            feature_value.append(self._week_encoder.transform(weekday).tolist()[0])
        return np.array(feature_value)

    def get_activity_names(self):
        return self._activity_names


if __name__ == '__main__':
    data_path = '../data/bpic2012_filter_w_pred_v2_1.csv'
    df = pd.read_csv(data_path)
    trace = Trace(case_id=182272)
    print(trace.random_pick_case_id())
